<template>
  <div id="app">
    <div class="upload">
      <input ref="file" type="file" @change="handleFile" />
      <el-button @click="handleUpload" :disabled="uploadDisabled"
        >上传</el-button
      >
    </div>
  </div>
</template>
<script>
import SparkMD5 from "spark-md5";
const chunkSize = 0.1 * 1024 * 1024; //切片大小
export default {
  name: "app",
  data() {
    return {
      container: {
        file: null, //整个文件
        hash: "", //文件hash值
        fileList: [], //所有切片
        chunksLength: 0, //所有的切片数量
        successUpload: 0, //已上传的切片数量
      },
      status: "wait", //当前状态
      fakeUploadPercentage: 0, //上传进度
      secondUpload: false, //是否秒传
    };
  },
  methods: {
    //获取文件
    handleFile() {
      let file = this.$refs.file.files[0];
      if (!file) return;
      console.log(file);
      this.container.file = file;
    },
    //上传文件
    async handleUpload() {
      if (!this.container.file) return;
      this.status = "uploading";
      //1.文件切片
      const fileList = [];
      let chunksLength = Math.cell(file.size / chunkSize);
      let currentIndex = 0;
      while (currentIndex < chunksLength) {
        fileList.push({
          file: file.slice(
            currentIndex * chunkSize,
            (currentIndex + 1) * chunkSize
          ),
        });
        currentIndex++;
      }
      this.container.fileList = fileList;
      this.container.chunksLength = chunksLength;
      //2.文件hash
      this.container.hash = await this.hashFile();
      let { data } = await this.hasUpload(
        this.container.file.name,
        this.container.hash
      );
      if (data.upload) {
        this.$message.success("秒传：上传成功");
        this.secondUpload = true;
        this.status = "success";
        this.fakeUploadPercentage = 100;
        return;
      }
      this.uploadList = this.container.fileList.map(({ file }, index) => ({
        fileHash: this.container.hash,
        index,
        name: this.container.hash + "-" + index,
        chunk: file,
        size: file.size,
      }));
      this.container.successUpload = data.hasUploadList.length;
      await this.uploadChunk(data.hasUploadList || []);
    },
    // 文件hash
    hashFile() {
      return new Promise((resolve, reject) => {
        const spark = new SparkMD5.ArrayBuffer();
        const reader = new FileReader();
        reader.readAsArrayBuffer(this.container.file);
        reader.onload = (e) => {
          spark.append(e.target.result);
          const sparkMd5 = new SparkMD5();
          sparkMd5.append(spark.end());
          sparkMd5.append(this.container.file.name);
          resolve(sparkMd5.end());
        };
      });
    },
    //获取已上传切片
    async hasUpload(fileName, fileHash) {
      let { data } = await this.request({
        url: "http://localhost:3000/api/hasUpload",
        headers: {
          "Content-Type": "application/json;charset=utf-8",
        },
        data: JSON.stringify({
          filename,
          fileHash,
        }),
      });
      return JSON.parse(data);
    },
    //上传切片，已上传的不再上传
    async uploadChunk(hasUploadList) {
      let requestList = this.uploadList
        .filter(({ name }) => !hasUploadList.includes(name))
        .map((data) => {
          let { chunk, name, index, fileHash } = data;
          let formData = new FormData();
          formData.append("name", name);
          formData.append("filename", this.container.file.name);
          formData.append("fileHash", fileHash);
          formData.append("file", chunk);
          return { formData, error: 0 };
        });
      this.sendRequest(requestList);
    },
    request({ url, method = "POST", data, headers = {}, onProgress }) {
      return new Promise((resolve, reject) => {
        const xhr = new XMLHttpRequest();
        xhr.open(method, url);
        xhr.upload.onprogress = onProgress;
        Object.keys(headers).forEach((item) => {
          xhr.setRequestHeader(item, headers[item]);
        });
        xhr.send(data);
        xhr.onload = (e) => {
          if (e.target.status == 200 || e.target.status == 304) {
            resolve({
              data: e.target.response,
            });
          } else {
            reject({
              data: e.target.response,
            });
          }
        };
      });
    },
  },
  computed: {
    uploadDisabled() {
      if (this.container.file && this.status == "wait") {
        return false;
      }
      return true;
    },
  },
};
</script>
<style scoped>
#app {
  padding: 50px 200px;
}
.upload {
  margin-bottom: 50px;
}
</style>